/*  mudmap - A tool to create and organize user side maps for MUD games
 *  Copyright (C) 2012  Neop (aka moemneop, email: mneop@web.de)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "world.h"

bool world::static_initialized = false;
map<string, string> world::way_names;
map<string, dir_t> world::way_str_dir_t;
map<dir_t, string> world::way_dir_t_str;
map<string, string> world::way_str_mnemonic;

place::place(int id, string name, world *w){
    this->w = w;
    this->id = id;
    this->name = name;
    rec_lvl_min = rec_lvl_max = area = layer = -1;
    danger_lvl = 0;
    has_shop = has_teacher = has_free_food = has_free_beverages = false;
};

void place::add_way(string oexit, string fexit, shared_ptr<place> pl){
    ways[oexit] = pl->id;
    pl->ways[fexit] = id;
}

void place::remove_way_to(int id){
    map<string, int> tmp;
    for(map<string, int>::iterator it = ways.begin(); it != ways.end(); it++) if(it->second != id) tmp[it->first] = it->second;
    ways = tmp;
    //for(map<string, int>::iterator it = ways.begin(); it != ways.end(); it++) if(it->second == id) it->second = -1;
}

string place::get_exit_to_place(int plid){
    for(map<string, int>::iterator it = ways.begin(); it != ways.end(); it++) if(plid == it->second) return it->first;
    return "";
}

void place::remove_subarea(int plid){
    vector<int> tmp;
    for(unsigned i = 0; i < children.size(); i++) if(children[i] != plid) tmp.push_back(children[i]);
    children = tmp;
}

void place::remove_parent(int plid){
    vector<int> tmp;
    for(unsigned i = 0; i < parents.size(); i++) if(parents[i] != plid) tmp.push_back(parents[i]);
    parents = tmp;
}

/// true if exit is not a user-defined exit
bool place::get_exit_default(string exit){
    if(exit == "n" || exit == "e" || exit == "s" || exit == "w" || exit == "ne" || exit == "nw" || exit == "se" || exit == "sw") return true;
    return false;
}

void layer::update_minmax(shared_ptr<place> pl){
    if(pl->posx < x_min) x_min = pl->posx;
    if(pl->posx > x_max) x_max = pl->posx;
    if(pl->posy < y_min) y_min = pl->posy;
    if(pl->posy > y_max) y_max = pl->posy;
}

world::world(string file){
    if(!static_initialized){
        static_initialized = true;
        way_names["n"] = _("north");
        way_names["e"] = _("east");
        way_names["s"] = _("south");
        way_names["w"] = _("west");
        way_names["ne"] = _("northeast");
        way_names["nw"] = _("northwest");
        way_names["se"] = _("southeast");
        way_names["sw"] = _("southwest");
        way_names["u"] = _("up");
        way_names["d"] = _("down");
        way_str_dir_t["n"] = dir_t::DIR_N;
        way_str_dir_t["e"] = dir_t::DIR_E;
        way_str_dir_t["s"] = dir_t::DIR_S;
        way_str_dir_t["w"] = dir_t::DIR_W;
        way_str_dir_t["ne"] = dir_t::DIR_NE;
        way_str_dir_t["nw"] = dir_t::DIR_NW;
        way_str_dir_t["se"] = dir_t::DIR_SE;
        way_str_dir_t["sw"] = dir_t::DIR_SW;
        way_str_dir_t["u"] = dir_t::DIR_U;
        way_str_dir_t["d"] = dir_t::DIR_D;
        way_dir_t_str[dir_t::DIR_N] = "n";
        way_dir_t_str[dir_t::DIR_S] = "s";
        way_dir_t_str[dir_t::DIR_E] = "e";
        way_dir_t_str[dir_t::DIR_W] = "w";
        way_dir_t_str[dir_t::DIR_NE] = "ne";
        way_dir_t_str[dir_t::DIR_NW] = "nw";
        way_dir_t_str[dir_t::DIR_SE] = "ne";
        way_dir_t_str[dir_t::DIR_SW] = "nw";
        way_dir_t_str[dir_t::DIR_U] = "u";
        way_dir_t_str[dir_t::DIR_D] = "d";
        way_str_mnemonic["n"] = _("_n");
        way_str_mnemonic["e"] = _("_e");
        way_str_mnemonic["s"] = _("_s");
        way_str_mnemonic["w"] = _("_w");
        way_str_mnemonic["ne"] = _("ne");
        way_str_mnemonic["nw"] = _("nw");
        way_str_mnemonic["se"] = _("se");
        way_str_mnemonic["sw"] = _("sw");
        way_str_mnemonic["u"] = _("_u");
        way_str_mnemonic["d"] = _("_d");
    }

    this->file = file;
    way_col = color(0, 255, 0);
    danger_lvl_col[0] = pair<color, string>(color(188, 188, 188), _("not evaluated"));
    danger_lvl_col[1] = pair<color, string>(color(0  , 255,   0), _("secure"));
    danger_lvl_col[2] = pair<color, string>(color(255, 255,   0), _("mobs don't attack"));
    danger_lvl_col[3] = pair<color, string>(color(255, 128,   0), _("mobs might attack"));
    danger_lvl_col[4] = pair<color, string>(color(255,   0,   0), _("mobs will attack"));
    if(file != "") load();
}

world::~world(){
    //if(file != "") save();
}

shared_ptr<place> world::get_place(int id){
    return places[id];
}

shared_ptr<place> world::get_place(int x, int y, int lay){
    if(layers[lay] == nullptr) return nullptr;
    return layers[lay]->get(x, y);
}

shared_ptr<layer> world::get_layer(int id){
    if(layers[id] == nullptr) layers[id] = shared_ptr<layer>(new layer(id));
    return layers[id];
}

shared_ptr<area> world::get_area(int id){
    return areas[id];
}

shared_ptr<shop> world::get_shop(int id){
    return shops[id];
}

shared_ptr<teacher> world::get_teacher(int id){
    return teachers[id];
}

vector<pair<int, shared_ptr<area> > > world::get_area_vec(){
    vector<pair<int, shared_ptr<area> > > ret;
    for(map<int, shared_ptr<area> >::iterator it = areas.begin(); it != areas.end(); it++) if(it->second != nullptr) ret.push_back(*it);
    return ret;
}

void world::load(){
    vector<string> lines = readfile_sv(file, true);
    if(lines.size() == 0) lines = readfile_sv(file.substr(file.rfind(path::worlds_dir_str)), true);
    int cur_area, cur_place;
    vector<pair<int, int> > parent_places;

    bool isset_way_ndef_col = false;

    for(unsigned i = 0; i < lines.size(); i++){
        if(config_comp(lines[i], "ver")){
            int major, minor, build;
            sscanf(lines[i].c_str(), "ver %d.%d.%d", &major, &minor, &build);
            if(major > AutoVersion::MAJOR || (major == AutoVersion::MAJOR && minor > AutoVersion::MINOR) || (major == AutoVersion::MAJOR && minor == AutoVersion::MINOR && build > AutoVersion::BUILD)){
                cout << "Warning: World file " << file << " was created with a newer version of MUD Map (" << major << '.' << minor << " > " << AutoVersion::FULLVERSION_STRING << ')' << endl;
                stringstream file_backup;
                file_backup << file << '.' << major << '.' << minor << '.' << build << '.' << "backup";
                stringstream command;
                #ifdef __linux__
                    command << "cp " << file << ' ' << file_backup;
                #else
                    command << "copy " << file << ' ' << file_backup;
                #endif
                cout << "Backing up " << file << " to " << file_backup.str() << ", executing " << command.str() << endl;

            }
        } else if(config_comp(lines[i], "wname")){
            name = lines[i].substr(6);
        } else if(config_comp(lines[i], "wcol")){
            int r, g, b;
            sscanf(lines[i].c_str(), "wcol %d %d %d", &r, &g, &b);
            way_col = color(r, g, b);
            if(!isset_way_ndef_col) way_ndefault_col = way_col;
        } else if(config_comp(lines[i], "wcnd")){
            int r, g, b;
            sscanf(lines[i].c_str(), "wcnd %d %d %d", &r, &g, &b);
            way_ndefault_col = color(r, g, b);
            isset_way_ndef_col = true;
        } else if(config_comp(lines[i], "home")){
            sscanf(lines[i].c_str(), "home %d %f %f", &home_layer, &home_x, &home_y);
        } else if(config_comp(lines[i], "dlc")){
            int id, r, g, b;
            string desc;
            char s[256];
            stringstream ss(lines[i]);
            ss >> desc >> id >> r >> g >> b;
            ss.get(s, 256);
            desc = s;
            desc.erase(0, 1);
            //sscanf(lines[i].c_str(), "dlc %d %d %d %d", &id, &r, &g, &b);
            danger_lvl_col[id] = pair<color, string>(color(r, g, b), desc);
        } else if(config_comp(lines[i], "a ")){
            sscanf(lines[i].c_str(), "a %d", &cur_area);
            areas[cur_area] = shared_ptr<area>(new area(cur_area, lines[i].substr(lines[i].find(' ', 2) + 1)));
        } else if(config_comp(lines[i], "acol")){
            int r, g, b;
            sscanf(lines[i].c_str(), "acol %d %d %d", &r, &g, &b);
            areas[cur_area]->col = color(r, g, b);
        } else if(config_comp(lines[i], "p ")){
            sscanf(lines[i].c_str(), "p %d", &cur_place);
            places[cur_place] = shared_ptr<place>(new place(cur_place, lines[i].substr(lines[i].find(' ', 2) + 1), this));
        } else if(config_comp(lines[i], "ppos")){
            int lay, x, y;
            if(places[cur_place]){
                sscanf(lines[i].c_str(), "ppos %d %d %d", &lay, &x, &y);
                places[cur_place]->layer = lay;
                places[cur_place]->posx = x;
                places[cur_place]->posy = y;
                if(layers[lay] == nullptr) layers[lay] = shared_ptr<layer>(new layer(lay));
                layers[lay]->set(places[cur_place], x, y);
                layers[lay]->update_minmax(places[cur_place]);
            }
        } else if(config_comp(lines[i], "par")){ // area
            if(places[cur_place]) sscanf(lines[i].c_str(), "par %d", &places[cur_place]->area);
        } else if(config_comp(lines[i], "pb")){ // bool value
            if(places[cur_place]){
                if(lines[i][3] == 's') places[cur_place]->has_shop = true;
                if(lines[i][3] == 't') places[cur_place]->has_teacher = true;
                if(lines[i][3] == 'f') places[cur_place]->has_free_food = true;
                if(lines[i][3] == 'b') places[cur_place]->has_free_beverages = true;
            }
        } else if(config_comp(lines[i], "pw")){
            int p;
            sscanf(lines[i].c_str(), "pw %d", &p);
            string t = lines[i].substr(lines[i].find(" ", 3) + 1);
            if(places[cur_place] && p >= 0) places[cur_place]->ways[t] = p;
        } else if(config_comp(lines[i], "pchi")){ // children places
            int id;
            sscanf(lines[i].c_str(), "pchi %d", &id);
            parent_places.push_back(pair<int, int>(id, cur_place));
            if(places[cur_place]) places[cur_place]->children.push_back(id);
        } else if(config_comp(lines[i], "pdl")){ // danger level
            if(places[cur_place]) sscanf(lines[i].c_str(), "pdl %d", &places[cur_place]->danger_lvl);
        } else if(config_comp(lines[i], "prl")){ // recommended level
            int min, max;
            if(places[cur_place]) sscanf(lines[i].c_str(), "prl %d %d", &min, &max);
            places[cur_place]->rec_lvl_min = min;
            places[cur_place]->rec_lvl_max = max;
        } else if(config_comp(lines[i], "pcom")){ // comment / item
            if(places[cur_place]) places[cur_place]->comments.push_back(lines[i].substr(5));
        }
    }
    for(unsigned i = 0; i < parent_places.size(); i++) places[parent_places[i].first]->parents.push_back(parent_places[i].second);
}

void world::save(){
    world tmp_world("");
    area tmp_area(0, "");
    place tmp_place(0, "", this);

    map<int, bool> used_areas;
    for(map<int, shared_ptr<place> >::iterator it = places.begin(); it != places.end(); it++) if(it->second != nullptr) used_areas[it->second->area] = true;

    ofstream ofs(file, ios::trunc);
    if(ofs.is_open() == true){
        ofs << "// MUD Map world file" << endl << "ver " << AutoVersion::FULLVERSION_STRING << endl;
        ofs << "wname " << name << endl;
        ofs << "wcol " << way_col.r << " " << way_col.g << " " << way_col.b << endl;
        ofs << "wcnd " << way_ndefault_col.r << " " << way_ndefault_col.g << " " << way_ndefault_col.b << endl;
        ofs << "home " << home_layer << " " << home_x << " " << home_y << endl;
        vector<int> dlc = get_danger_level_vec();
        for(unsigned i = 0; i < dlc.size(); i++) if((danger_lvl_col[dlc[i]].first != tmp_world.danger_lvl_col[dlc[i]].first || danger_lvl_col[dlc[i]].second != tmp_world.danger_lvl_col[dlc[i]].second) && danger_lvl_col[dlc[i]].second != "") ofs << "dlc " << dlc[i] << " " << danger_lvl_col[dlc[i]].first.r << " " << danger_lvl_col[dlc[i]].first.g << " " << danger_lvl_col[dlc[i]].first.b << " " << danger_lvl_col[dlc[i]].second << endl;
        for(map<int, shared_ptr<area> >::iterator it = areas.begin(); it != areas.end(); it++){
            if(it->second != nullptr && used_areas.count(it->second->id) > 0){
                ofs << "a " << it->second->id << " " << it->second->name << endl;
                if(it->second->col != tmp_area.col) ofs << "acol " << it->second->col.r << " " << it->second->col.g << " " << it->second->col.b << endl;
            }
        }
        for(map<int, shared_ptr<place> >::iterator it = places.begin(); it != places.end(); it++){
            if(it->second != nullptr){
                if(it->second->layer == -1) it->second->layer = get_new_layer_id();
                ofs << "p " << it->second->id << " " << it->second->name << endl;
                ofs << "ppos " << it->second->layer << " " << it->second->posx << " " << it->second->posy << endl;
                if(it->second->area != tmp_place.area) ofs << "par " << it->second->area << endl;
                if(it->second->has_shop) ofs << "pb s" << endl;
                if(it->second->has_teacher) ofs << "pb t" << endl;
                if(it->second->has_free_food) ofs << "pb f" << endl;
                if(it->second->has_free_beverages) ofs << "pb b" << endl;
                for(map<string, int>::iterator itw = it->second->ways.begin(); itw != it->second->ways.end(); itw++) ofs << "pw " << itw->second << " " << itw->first << endl;
                for(unsigned i = 0; i < it->second->children.size(); i++) ofs << "pchi " << it->second->children[i] << endl;
                if(it->second->danger_lvl != tmp_place.danger_lvl) ofs << "pdl " << it->second->danger_lvl << endl;
                if(it->second->rec_lvl_min != tmp_place.rec_lvl_min || it->second->rec_lvl_max != tmp_place.rec_lvl_max) ofs << "prl " << it->second->rec_lvl_min << " " << it->second->rec_lvl_max << endl;
                for(list<string>::iterator itcomment = it->second->comments.begin(); itcomment != it->second->comments.end(); itcomment++){
                    ofs << "pcom " << *itcomment << endl;
                }
            }
        }
    }
}

vector<int> world::get_danger_level_vec(){
    vector<int> ret;
    for(map<int, pair<color, string> >::iterator it = danger_lvl_col.begin(); it != danger_lvl_col.end(); it++) if(it->second.second != "") ret.push_back(it->first);
    return ret;
}

vector<pair<int, shared_ptr<place> > > world::get_place_vec_sorted(){
    list<pair<string, int> > pllist;
    vector<pair<int, shared_ptr<place> > > ret;
    for(map<int, shared_ptr<place> >::iterator it = places.begin(); it != places.end(); it++) if(it->second != nullptr) pllist.push_back(pair<string, int>(it->second->name, it->first));
    pllist.sort();
    for(list<pair<string, int> >::iterator it = pllist.begin(); it != pllist.end(); it++) ret.push_back(pair<int, shared_ptr<place> >(it->second, places[it->second]));
    return ret;
}

vector<pair<int, shared_ptr<place> > > world::get_place_vec(){
    vector<pair<int, shared_ptr<place> > > ret;
    for(map<int, shared_ptr<place> >::iterator it = places.begin(); it != places.end(); it++) ret.push_back(pair<int, shared_ptr<place> >(it->first, it->second));
    return ret;
}

vector<pair<int, shared_ptr<area> > > world::get_area_vec_sorted(){
    list<pair<string, int> > arlist;
    vector<pair<int, shared_ptr<area> > > ret;
    for(map<int, shared_ptr<area> >::iterator it = areas.begin(); it != areas.end(); it++) if(it->second != nullptr) arlist.push_back(pair<string, int>(it->second->name, it->first));
    arlist.sort();
    for(list<pair<string, int> >::iterator it = arlist.begin(); it != arlist.end(); it++) ret.push_back(pair<int, shared_ptr<area> >(it->second, areas[it->second]));
    return ret;
}

color world::get_danger_level_col(int id){
    return danger_lvl_col[id].first;
}

string world::get_danger_level_desc(int id){
    return danger_lvl_col[id].second;
}

void world::add_place(shared_ptr<place> pl){
    int id = pl->id;
    if(places[id] != nullptr) for(id = 0; places[id] == nullptr; id++);
    pl->id = id;
    places[id] = pl;
    if(layers[pl->layer] == nullptr) layers[pl->layer] = shared_ptr<layer>(new layer(pl->layer));
    layers[pl->layer]->set(pl, pl->posx, pl->posy);
    layers[pl->layer]->update_minmax(pl);
}

void world::add_area(shared_ptr<area> ar){
    int id = ar->id;
    if(areas[id] != nullptr) for(id = 0; areas[id] == nullptr; id++);
    ar->id = id;
    areas[id] = ar;
}

void world::remove_place(shared_ptr<place> pl){
    for(map<int, shared_ptr<place> >::iterator it = places.begin(); it != places.end(); it++) if(it->second) it->second->remove_subarea(pl->id);
    for(map<string, int>::iterator it = pl->ways.begin(); it != pl->ways.end(); it++) places[it->second]->remove_way_to(pl->id);
    layers[pl->layer]->remove(pl->posx, pl->posy);
    places[pl->id] = nullptr;
}

void world::move_place(shared_ptr<place> pl, int x, int y, int lay){
    layers[pl->layer]->remove(pl->posx, pl->posy);
    layers[lay]->set(pl, x, y);
    pl->layer = lay;
    pl->posx = x;
    pl->posy = y;
}

void world::add_way(shared_ptr<place> pl1, string exit1, shared_ptr<place> pl2, string exit2){
    pl1->add_way(exit1, exit2, pl2);
}

void world::remove_way(shared_ptr<place> pl, string exit){
    places[pl->ways[exit]]->remove_way_to(pl->id);
    pl->remove_way_to(pl->ways[exit]);
}

shared_ptr<place> world::get_way(shared_ptr<place> pl, string exit){
    return places[pl->ways[exit]];
}

vector<pair<string, shared_ptr<place> > > world::get_ways(shared_ptr<place> pl){
    vector<pair<string, shared_ptr<place> > > ret;
    for(map<string, int>::iterator it = pl->ways.begin(); it != pl->ways.end(); it++) if(it->second != -1) ret.push_back(pair<string, shared_ptr<place> >(it->first, places[it->second]));
    return ret;
}

int world::get_new_layer_id(){
    int ret = 0;
    while(layers[ret] != nullptr) ret++;
    return ret;
}

int world::get_new_place_id(){
    int ret = 0;
    while(places[ret] != nullptr) ret++;
    return ret;
}

int world::get_new_area_id(){
    int ret = 0;
    while(areas[ret] != nullptr) ret++;
    return ret;
}

void world::set_danger_level(int id, string desc, color col){
    if(id == -1) for(id = 0; danger_lvl_col[id].second != ""; id++);
    danger_lvl_col[id] = pair<color, string>(col, desc);
}
